home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / css.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  45.2 KB  |  1,496 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10. const nsIDOMCSSStyleRule = Ci.nsIDOMCSSStyleRule;
  11. const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
  12. const nsISelectionDisplay = Ci.nsISelectionDisplay;
  13. const nsISelectionController = Ci.nsISelectionController;
  14.  
  15. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  16.  
  17. var domUtils = null;
  18. try {
  19.     domUtils = CCSV("@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
  20. } catch (exc) {
  21.     // We can try to live without "dom-utils", since it only comes with DOM Inspector
  22. }
  23.  
  24. var CSSPropTag =
  25.     DIV({class: "cssProp editGroup", $disabledStyle: "$prop.disabled"},
  26.         SPAN({class: "cssPropName editable"}, "$prop.name"),
  27.         SPAN({class: "cssColon"}, ":"),
  28.         SPAN({class: "cssPropValue editable"}, "$prop.value$prop.important"),
  29.         SPAN({class: "cssSemi"}, ";")
  30.     );
  31.  
  32. var CSSRuleTag =
  33.     TAG("$rule.tag", {rule: "$rule"});
  34.  
  35. var CSSImportRuleTag =
  36.     DIV({class: "cssRule insertInto", _repObject: "$rule.rule"},
  37.         "@import "",
  38.         A({class: "objectLink", _repObject: "$rule.rule.styleSheet"}, "$rule.rule.href"),
  39.         "";"
  40.     );
  41.  
  42. var CSSStyleRuleTag =
  43.     DIV({class: "cssRule insertInto", _repObject: "$rule.rule.style",
  44.             "ruleId": "$rule.id"},
  45.         DIV({class: "cssHead"},
  46.             SPAN({class: "cssSelector"}, "$rule.selector"), " {"
  47.         ),
  48.         FOR("prop", "$rule.props",
  49.             CSSPropTag
  50.         ),
  51.         DIV({class: "editable insertBefore"}, "}")
  52.     );
  53.  
  54. const reSplitCSS =  /(url\("?[^"\)]+?"?\))|(rgb\(.*?\))|(#[\dA-Fa-f]+)|(-?\d+(\.\d+)?(%|[a-z]{1,2})?)|([^,\s]+)|"(.*?)"/;
  55.  
  56. const reURL = /url\("?([^"\)]+)?"?\)/;
  57.  
  58. const reRepeat = /no-repeat|repeat-x|repeat-y|repeat/;
  59.  
  60. const sothinkInstalled = !!$("swfcatcherKey_sidebar");
  61. const styleGroups =
  62. {
  63.     text: [
  64.         "font-family",
  65.         "font-size",
  66.         "font-weight",
  67.         "font-style",
  68.         "color",
  69.         "text-transform",
  70.         "text-decoration",
  71.         "letter-spacing",
  72.         "word-spacing",
  73.         "line-height",
  74.         "text-align",
  75.         "vertical-align",
  76.         "direction",
  77.         "column-count",
  78.         "column-gap",
  79.         "column-width"
  80.     ],
  81.  
  82.     background: [
  83.         "background-color",
  84.         "background-image",
  85.         "background-repeat",
  86.         "background-position",
  87.         "background-attachment",
  88.         "opacity"
  89.     ],
  90.  
  91.     box: [
  92.         "width",
  93.         "height",
  94.         "top",
  95.         "right",
  96.         "bottom",
  97.         "left",
  98.         "margin-top",
  99.         "margin-right",
  100.         "margin-bottom",
  101.         "margin-left",
  102.         "padding-top",
  103.         "padding-right",
  104.         "padding-bottom",
  105.         "padding-left",
  106.         "border-top-width",
  107.         "border-right-width",
  108.         "border-bottom-width",
  109.         "border-left-width",
  110.         "border-top-color",
  111.         "border-right-color",
  112.         "border-bottom-color",
  113.         "border-left-color",
  114.         "border-top-style",
  115.         "border-right-style",
  116.         "border-bottom-style",
  117.         "border-left-style",
  118.         "-moz-border-top-radius",
  119.         "-moz-border-right-radius",
  120.         "-moz-border-bottom-radius",
  121.         "-moz-border-left-radius",
  122.         "outline-top-width",
  123.         "outline-right-width",
  124.         "outline-bottom-width",
  125.         "outline-left-width",
  126.         "outline-top-color",
  127.         "outline-right-color",
  128.         "outline-bottom-color",
  129.         "outline-left-color",
  130.         "outline-top-style",
  131.         "outline-right-style",
  132.         "outline-bottom-style",
  133.         "outline-left-style"
  134.     ],
  135.  
  136.     layout: [
  137.         "position",
  138.         "display",
  139.         "visibility",
  140.         "z-index",
  141.         "overflow-x",  // http://www.w3.org/TR/2002/WD-css3-box-20021024/#overflow
  142.         "overflow-y",
  143.         "overflow-clip",
  144.         "white-space",
  145.         "clip",
  146.         "float",
  147.         "clear",
  148.         "-moz-box-sizing"
  149.     ],
  150.  
  151.     other: [
  152.         "cursor",
  153.         "list-style-image",
  154.         "list-style-position",
  155.         "list-style-type",
  156.         "marker-offset",
  157.         "user-focus",
  158.         "user-select",
  159.         "user-modify",
  160.         "user-input"
  161.     ]
  162. };
  163.  
  164. // ************************************************************************************************
  165.  
  166. Firebug.CSSStyleSheetPanel = function() {}
  167.  
  168. Firebug.CSSStyleSheetPanel.prototype = extend(Firebug.SourceBoxPanel,
  169. {
  170.     template: domplate(
  171.     {
  172.         tag:
  173.             FOR("rule", "$rules",
  174.                 CSSRuleTag
  175.             )
  176.     }),
  177.  
  178.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  179.  
  180.     refresh: function()
  181.     {
  182.         if (this.location)
  183.             this.updateLocation(this.location);
  184.         else if (this.selection)
  185.             this.updateSelection(this.selection);
  186.     },
  187.  
  188.     toggleEditing: function()
  189.     {
  190.         if (!this.stylesheetEditor)
  191.             this.stylesheetEditor = new StyleSheetEditor(this.document);
  192.  
  193.         if (this.editing)
  194.             Firebug.Editor.stopEditing();
  195.         else
  196.         {
  197.             if (!this.location)
  198.                 return;
  199.  
  200.             var styleSheet = this.location.editStyleSheet
  201.                 ? this.location.editStyleSheet.sheet
  202.                 : this.location;
  203.  
  204.             var css = getStyleSheetCSS(styleSheet, this.context);
  205.             //var topmost = getTopmostRuleLine(this.panelNode);
  206.  
  207.             this.stylesheetEditor.styleSheet = this.location;
  208.             Firebug.Editor.startEditing(this.panelNode, css, this.stylesheetEditor);
  209.             //this.stylesheetEditor.scrollToLine(topmost.line, topmost.offset);
  210.         }
  211.     },
  212.  
  213.     getStylesheetURL: function(style)
  214.     {
  215.         if (this.location.href)
  216.             return this.location.href;
  217.         else
  218.             return this.context.window.location.href;
  219.     },
  220.  
  221.     getRuleByLine: function(styleSheet, line)
  222.     {
  223.         if (!domUtils)
  224.             return null;
  225.  
  226.         var cssRules = styleSheet.cssRules;
  227.         for (var i = 0; i < cssRules.length; ++i)
  228.         {
  229.             var rule = cssRules[i];
  230.             if (rule instanceof CSSStyleRule)
  231.             {
  232.                 var ruleLine = domUtils.getRuleLine(rule);
  233.                 if (ruleLine >= line)
  234.                     return rule;
  235.             }
  236.         }
  237.     },
  238.  
  239.     highlightRule: function(rule)
  240.     {
  241.         var ruleElement = Firebug.getElementByRepObject(this.panelNode, rule.style);
  242.         if (ruleElement)
  243.         {
  244.             scrollIntoCenterView(ruleElement, this.panelNode);
  245.             setClassTimed(ruleElement, "jumpHighlight", this.context);
  246.         }
  247.     },
  248.  
  249.     getStyleSheetRules: function(context, styleSheet)
  250.     {
  251.         function appendRules(cssRules)
  252.         {
  253.             for (var i = 0; i < cssRules.length; ++i)
  254.             {
  255.                 var rule = cssRules[i];
  256.                 if (rule instanceof CSSStyleRule)
  257.                 {
  258.                     var props = this.getRuleProperties(context, rule);
  259.                     var line = domUtils.getRuleLine(rule);
  260.                     var ruleId = rule.selectorText+"/"+line;
  261.                     rules.push({tag: CSSStyleRuleTag, rule: rule, id: ruleId,
  262.                                 selector: rule.selectorText, props: props});
  263.                 }
  264.                 else if (rule instanceof CSSImportRule)
  265.                     rules.push({tag: CSSImportRuleTag, rule: rule});
  266.                 else if (rule instanceof CSSMediaRule)
  267.                     appendRules.apply(this, [rule.cssRules]);
  268.             }
  269.         }
  270.  
  271.         var rules = [];
  272.         appendRules.apply(this, [styleSheet.cssRules]);
  273.         return rules;
  274.     },
  275.  
  276.     getRuleProperties: function(context, rule, inheritMode)
  277.     {
  278.         var props = [];
  279.  
  280.         var ruleRE = /\{(.*?)\}$/;
  281.         var m = ruleRE.exec(rule.cssText);
  282.         if (!m)
  283.             return props;
  284.  
  285.         var propRE = /\s*([^:\s]*?)\s*:\s*(.*?)\s*(! important)?$/;
  286.  
  287.         var lines = m[1].split(";");
  288.         for (var i = 0; i < lines.length-1; ++i)
  289.         {
  290.             var m = propRE.exec(lines[i]);
  291.             if (!m)
  292.                 continue;
  293.  
  294.             var name = m[1], value = m[2], important = !!m[3];
  295.             if (value)
  296.                 this.addProperty(name, value, important, false, inheritMode, props);
  297.         }
  298.  
  299.         var line = domUtils.getRuleLine(rule);
  300.         var ruleId = rule.selectorText+"/"+line;
  301.         this.addOldProperties(context, ruleId, inheritMode, props);
  302.         sortProperties(props);
  303.  
  304.         return props;
  305.     },
  306.  
  307.     addOldProperties: function(context, ruleId, inheritMode, props)
  308.     {
  309.         if (context.selectorMap && context.selectorMap.hasOwnProperty(ruleId) )
  310.         {
  311.             var moreProps = context.selectorMap[ruleId];
  312.             for (var i = 0; i < moreProps.length; ++i)
  313.             {
  314.                 var prop = moreProps[i];
  315.                 this.addProperty(prop.name, prop.value, prop.important, true, inheritMode, props);
  316.             }
  317.         }
  318.     },
  319.  
  320.     addProperty: function(name, value, important, disabled, inheritMode, props)
  321.     {
  322.         if (inheritMode && !inheritedStyleNames[name])
  323.             return;
  324.  
  325.         name = this.translateName(name, value);
  326.         if (name)
  327.         {
  328.             value = stripUnits(rgbToHex(value));
  329.             important = important ? " !important" : "";
  330.  
  331.             var prop = {name: name, value: value, important: important, disabled: disabled};
  332.             props.push(prop);
  333.         }
  334.     },
  335.  
  336.     translateName: function(name, value)
  337.     {
  338.         // Don't show these proprietary Mozilla properties
  339.         if ((value == "-moz-initial"
  340.             && (name == "-moz-background-clip" || name == "-moz-background-origin"
  341.                 || name == "-moz-background-inline-policy"))
  342.         || (value == "physical"
  343.             && (name == "margin-left-ltr-source" || name == "margin-left-rtl-source"
  344.                 || name == "margin-right-ltr-source" || name == "margin-right-rtl-source"))
  345.         || (value == "physical"
  346.             && (name == "padding-left-ltr-source" || name == "padding-left-rtl-source"
  347.                 || name == "padding-right-ltr-source" || name == "padding-right-rtl-source")))
  348.             return null;
  349.  
  350.         // Translate these back to the form the user probably expects
  351.         if (name == "margin-left-value")
  352.             return "margin-left";
  353.         else if (name == "margin-right-value")
  354.             return "margin-right";
  355.         else if (name == "margin-top-value")
  356.             return "margin-top";
  357.         else if (name == "margin-bottom-value")
  358.             return "margin-bottom";
  359.         else if (name == "padding-left-value")
  360.             return "padding-left";
  361.         else if (name == "padding-right-value")
  362.             return "padding-right";
  363.         else if (name == "padding-top-value")
  364.             return "padding-top";
  365.         else if (name == "padding-bottom-value")
  366.             return "padding-bottom";
  367.         // XXXjoe What about border!
  368.         else
  369.             return name;
  370.     },
  371.  
  372.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  373.  
  374.     editElementStyle: function()
  375.     {
  376.         var elementStyle = this.selection.style;
  377.         var rulesBox = this.panelNode.firstChild;
  378.         var styleRuleBox = Firebug.getElementByRepObject(rulesBox, elementStyle);
  379.         if (styleRuleBox)
  380.             Firebug.Editor.insertRowForObject(styleRuleBox);
  381.         else
  382.         {
  383.             var rule = {rule: this.selection, inherited: false, selector: "element.style", props: []};
  384.             var styleRuleBox = this.template.ruleTag.replace({rule: rule}, this.document);
  385.  
  386.             if (rulesBox.firstChild)
  387.                 rulesBox.insertBefore(styleRuleBox, rulesBox.firstChild);
  388.             else
  389.                 rulesBox.appendChild(styleRuleBox);
  390.  
  391.             Firebug.Editor.insertRowForObject(styleRuleBox);
  392.         }
  393.     },
  394.  
  395.     insertPropertyRow: function(row)
  396.     {
  397.         Firebug.Editor.insertRowForObject(row);
  398.     },
  399.  
  400.     editPropertyRow: function(row)
  401.     {
  402.         var propValueBox = getChildByClass(row, "cssPropValue");
  403.         Firebug.Editor.startEditing(propValueBox);
  404.     },
  405.  
  406.     deletePropertyRow: function(row)
  407.     {
  408.         var style = Firebug.getRepObject(row);
  409.         var propName = getChildByClass(row, "cssPropName").textContent;
  410.         style.removeProperty(propName);
  411.  
  412.         // Remove the property from the selector map, if it was disabled
  413.         var ruleId = Firebug.getRepNode(row).getAttribute("ruleId");
  414.         if ( this.context.selectorMap && this.context.selectorMap.hasOwnProperty(ruleId) )
  415.         {
  416.             var map = this.context.selectorMap[ruleId];
  417.             for (var i = 0; i < map.length; ++i)
  418.             {
  419.                 if (map[i].name == propName)
  420.                 {
  421.                     map.splice(i, 1);
  422.                     break;
  423.                 }
  424.             }
  425.         }
  426.  
  427.         row.parentNode.removeChild(row);
  428.  
  429.         this.markChange(this.name == "stylesheet");
  430.     },
  431.  
  432.     disablePropertyRow: function(row)
  433.     {
  434.         toggleClass(row, "disabledStyle");
  435.  
  436.         var style = Firebug.getRepObject(row);
  437.         var propName = getChildByClass(row, "cssPropName").textContent;
  438.  
  439.         if (!this.context.selectorMap)
  440.             this.context.selectorMap = {};
  441.  
  442.         // XXXjoe Generate unique key for elements too
  443.         var ruleId = Firebug.getRepNode(row).getAttribute("ruleId");
  444.         if (!(this.context.selectorMap.hasOwnProperty(ruleId)))
  445.             this.context.selectorMap[ruleId] = [];
  446.  
  447.         var map = this.context.selectorMap[ruleId];
  448.         var propValue = getChildByClass(row, "cssPropValue").textContent;
  449.         var parsedValue = parsePriority(propValue);
  450.  
  451.         if (hasClass(row, "disabledStyle"))
  452.         {
  453.             style.removeProperty(propName);
  454.  
  455.             map.push({"name": propName, "value": parsedValue.value,
  456.                 "important": parsedValue.priority});
  457.         }
  458.         else
  459.         {
  460.             style.setProperty(propName, parsedValue.value, parsedValue.priority);
  461.  
  462.             var index = findPropByName(map, propName);
  463.             map.splice(index, 1);
  464.         }
  465.  
  466.         this.markChange(this.name == "stylesheet");
  467.     },
  468.  
  469.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  470.  
  471.     onMouseDown: function(event)
  472.     {
  473.         // XXjoe Hack to only allow clicking on the checkbox
  474.         if (!isLeftClick(event) || event.clientX > 20)
  475.             return;
  476.  
  477.         if (hasClass(event.target, "textEditor"))
  478.             return;
  479.  
  480.         var row = getAncestorByClass(event.target, "cssProp");
  481.         if (row)
  482.         {
  483.             this.disablePropertyRow(row);
  484.             cancelEvent(event);
  485.         }
  486.     },
  487.  
  488.     onClick: function(event)
  489.     {
  490.         if (!isLeftClick(event) || event.clientX <= 20 || event.detail != 2)
  491.             return;
  492.  
  493.         var row = getAncestorByClass(event.target, "cssRule");
  494.         if (row && !getAncestorByClass(event.target, "cssPropName")
  495.             && !getAncestorByClass(event.target, "cssPropValue"))
  496.         {
  497.             this.insertPropertyRow(row);
  498.             cancelEvent(event);
  499.         }
  500.     },
  501.  
  502.  
  503.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  504.     // extends Panel
  505.  
  506.     name: "stylesheet",
  507.     parentPanel: null,
  508.     searchable: true,
  509.     dependents: ["css", "stylesheet", "dom", "domSide", "layout"],
  510.  
  511.     initialize: function()
  512.     {
  513.         this.onMouseDown = bind(this.onMouseDown, this);
  514.         this.onClick = bind(this.onClick, this);
  515.  
  516.         Firebug.Panel.initialize.apply(this, arguments);
  517.         this.initializeSourceBoxes();
  518.  
  519.     },
  520.  
  521.     destroy: function(state)
  522.     {
  523.         state.scrollTop = this.panelNode.scrollTop ? this.panelNode.scrollTop : this.lastScrollTop;
  524.  
  525.         persistObjects(this, state);
  526.  
  527.         Firebug.Editor.stopEditing();
  528.         Firebug.Panel.destroy.apply(this, arguments);
  529.     },
  530.  
  531.     initializeNode: function(oldPanelNode)
  532.     {
  533.         this.panelNode.addEventListener("mousedown", this.onMouseDown, false);
  534.         this.panelNode.addEventListener("click", this.onClick, false);
  535.     },
  536.  
  537.     destroyNode: function()
  538.     {
  539.         this.panelNode.removeEventListener("mousedown", this.onMouseDown, false);
  540.         this.panelNode.removeEventListener("click", this.onClick, false);
  541.     },
  542.  
  543.     show: function(state)
  544.     {
  545.         this.showToolbarButtons("fbCSSButtons", true);
  546.  
  547.         if (this.context.loaded && !this.location)
  548.         {
  549.             restoreObjects(this, state);
  550.  
  551.             if (state && state.scrollTop)
  552.                 this.panelNode.scrollTop = state.scrollTop;
  553.         }
  554.     },
  555.  
  556.     hide: function()
  557.     {
  558.         this.showToolbarButtons("fbCSSButtons", false);
  559.  
  560.         this.lastScrollTop = this.panelNode.scrollTop;
  561.     },
  562.  
  563.     supportsObject: function(object)
  564.     {
  565.         if (object instanceof CSSStyleSheet)
  566.             return 1;
  567.         else if (object instanceof CSSStyleRule)
  568.             return 2;
  569.         else if (object instanceof SourceLink && object.type == "css" && reCSS.test(object.href))
  570.             return 2;
  571.         else
  572.             return 0;
  573.     },
  574.  
  575.     updateLocation: function(styleSheet)
  576.     {
  577.         if (styleSheet.editStyleSheet)
  578.             styleSheet = styleSheet.editStyleSheet.sheet;
  579.  
  580.         var rules = this.getStyleSheetRules(this.context, styleSheet);
  581.         if (rules.length)
  582.             this.template.tag.replace({rules: rules}, this.panelNode);
  583.         else
  584.             FirebugReps.Warning.tag.replace({object: "EmptyStyleSheet"}, this.panelNode);
  585.     },
  586.  
  587.     updateSelection: function(object)
  588.     {
  589.         this.selection = null;
  590.  
  591.         if (object instanceof CSSStyleRule)
  592.         {
  593.             this.navigate(object.parentStyleSheet);
  594.             this.highlightRule(object);
  595.         }
  596.         else if (object instanceof CSSStyleSheet)
  597.         {
  598.             this.navigate(object);
  599.         }
  600.         else if (object instanceof SourceLink)
  601.         {
  602.             try
  603.             {
  604.                 clearNode(this.panelNode);  // replace rendered stylesheets
  605.                 this.showSourceFile(object);
  606.  
  607.                 var lineNo = object.line;
  608.                 if (lineNo)
  609.                     this.scrollToLine(lineNo, this.jumpHighlightFactory(lineNo, this.context));
  610.             }
  611.             catch(exc) {
  612.             }
  613.         }
  614.     },
  615.  
  616.     getLocationList: function()
  617.     {
  618.         var styleSheets = [];
  619.  
  620.         function addSheet(sheet)
  621.         {
  622.             var sheetLocation = getURLForStyleSheet(sheet);
  623.  
  624.             if (isSystemURL(sheetLocation) && Firebug.filterSystemURLs)
  625.                 return;
  626.  
  627.             styleSheets.push(sheet);
  628.  
  629.             for (var i = 0; i < sheet.cssRules.length; ++i)
  630.             {
  631.                 var rule = sheet.cssRules[i];
  632.                 if (rule instanceof CSSImportRule)
  633.                     addSheet(rule.styleSheet);
  634.             }
  635.         }
  636.  
  637.         var rootSheets = this.context.window.document.styleSheets;
  638.         for (var i = 0; i < rootSheets.length; ++i)
  639.             addSheet(rootSheets[i]);
  640.  
  641.         return styleSheets;
  642.     },
  643.  
  644.     getOptionsMenuItems: function()
  645.     {
  646.         return [
  647.             {label: "Refresh", command: bind(this.refresh, this) }
  648.         ];
  649.     },
  650.  
  651.     getContextMenuItems: function(style, target)
  652.     {
  653.         var items = [];
  654.  
  655.         if (this.infoTipType == "color")
  656.         {
  657.             items.push(
  658.                 {label: "CopyColor",
  659.                     command: bindFixed(copyToClipboard, FBL, this.infoTipObject) }
  660.             );
  661.         }
  662.         else if (this.infoTipType == "image")
  663.         {
  664.             items.push(
  665.                 {label: "CopyImageLocation",
  666.                     command: bindFixed(copyToClipboard, FBL, this.infoTipObject) },
  667.                 {label: "OpenImageInNewTab",
  668.                     command: bindFixed(openNewTab, FBL, this.infoTipObject) }
  669.             );
  670.         }
  671.  
  672.         if (this.selection instanceof Element)
  673.         {
  674.             items.push(
  675.                 "-",
  676.                 {label: "EditStyle",
  677.                     command: bindFixed(this.editElementStyle, this) }
  678.             );
  679.         }
  680.  
  681.         if (getAncestorByClass(target, "cssRule"))
  682.         {
  683.             items.push(
  684.                 "-",
  685.                 {label: "NewProp",
  686.                     command: bindFixed(this.insertPropertyRow, this, target) }
  687.             );
  688.  
  689.             var propRow = getAncestorByClass(target, "cssProp");
  690.             if (propRow)
  691.             {
  692.                 var propName = getChildByClass(propRow, "cssPropName").textContent;
  693.                 var isDisabled = hasClass(propRow, "disabledStyle");
  694.  
  695.                 items.push(
  696.                     {label: $STRF("EditProp", [propName]), nol10n: true,
  697.                         command: bindFixed(this.editPropertyRow, this, propRow) },
  698.                     {label: $STRF("DeleteProp", [propName]), nol10n: true,
  699.                         command: bindFixed(this.deletePropertyRow, this, propRow) },
  700.                     {label: $STRF("DisableProp", [propName]), nol10n: true,
  701.                         type: "checkbox", checked: isDisabled,
  702.                         command: bindFixed(this.disablePropertyRow, this, propRow) }
  703.                 );
  704.             }
  705.         }
  706.  
  707.         items.push(
  708.             "-",
  709.             {label: "Refresh", command: bind(this.refresh, this) }
  710.         );
  711.  
  712.         return items;
  713.     },
  714.  
  715.     browseObject: function(object)
  716.     {
  717.         if (this.infoTipType == "image")
  718.         {
  719.             openNewTab(this.infoTipObject);
  720.             return true;
  721.         }
  722.     },
  723.  
  724.     showInfoTip: function(infoTip, target, x, y)
  725.     {
  726.         var propValue = getAncestorByClass(target, "cssPropValue");
  727.         if (propValue)
  728.         {
  729.             var offset = getClientOffset(propValue);
  730.             var offsetX = x-offset.x;
  731.  
  732.             var text = propValue.textContent;
  733.             var charWidth = propValue.offsetWidth/text.length;
  734.             var charOffset = Math.floor(offsetX/charWidth);
  735.  
  736.             var cssValue = parseCSSValue(text, charOffset);
  737.             if (cssValue)
  738.             {
  739.                 if (cssValue.value == this.infoTipValue)
  740.                     return true;
  741.  
  742.                 this.infoTipValue = cssValue.value;
  743.  
  744.                 if (cssValue.type == "rgb" || (!cssValue.type && isColorKeyword(cssValue.value)))
  745.                 {
  746.                     this.infoTipType = "color";
  747.                     this.infoTipObject = cssValue.value;
  748.  
  749.                     return Firebug.InfoTip.populateColorInfoTip(infoTip, cssValue.value);
  750.                 }
  751.                 else if (cssValue.type == "url")
  752.                 {
  753.                     var style = Firebug.getRepObject(target);
  754.                     var baseURL = this.getStylesheetURL(style);
  755.                     var relURL = parseURLValue(cssValue.value);
  756.                     var absURL = absoluteURL(relURL, baseURL);
  757.                     var repeat = parseRepeatValue(text);
  758.  
  759.                     this.infoTipType = "image";
  760.                     this.infoTipObject = absURL;
  761.  
  762.                     return Firebug.InfoTip.populateImageInfoTip(infoTip, absURL, repeat);
  763.                 }
  764.             }
  765.         }
  766.  
  767.         delete this.infoTipType;
  768.         delete this.infoTipValue;
  769.         delete this.infoTipObject;
  770.     },
  771.  
  772.     getEditor: function(target, value)
  773.     {
  774.         if (!this.editor)
  775.             this.editor = new CSSEditor(this.document);
  776.  
  777.         return this.editor;
  778.     },
  779.  
  780.     getDefaultLocation: function()
  781.     {
  782.         if (!this.context.loaded)
  783.             return null;
  784.  
  785.         var styleSheets = this.context.window.document.styleSheets;
  786.         if (styleSheets.length)
  787.         {
  788.             var sheet = styleSheets[0];
  789.             return (Firebug.filterSystemURLs && isSystemURL(getURLForStyleSheet(sheet))) ? null : sheet;
  790.         }
  791.     },
  792.  
  793.     getObjectLocation: function(styleSheet)
  794.     {
  795.         return getURLForStyleSheet(styleSheet);
  796.     },
  797.  
  798.     search: function(text)
  799.     {
  800.         if (!text)
  801.         {
  802.             delete this.currentSearch;
  803.             return false;
  804.         }
  805.  
  806.         var row;
  807.         if (this.currentSearch && text == this.currentSearch.text)
  808.         {
  809.             row = this.currentSearch.findNext(true);
  810.         }
  811.         else
  812.         {
  813.             if (this.editing)
  814.             {
  815.                 this.currentSearch = new TextSearch(this.stylesheetEditor.box);
  816.                 row = this.currentSearch.find(text);
  817.  
  818.                 if (row)
  819.                 {
  820.                     var sel = this.document.defaultView.getSelection();
  821.                     sel.removeAllRanges();
  822.                     sel.addRange(this.currentSearch.range);
  823.                     scrollSelectionIntoView(this);
  824.                     return true;
  825.                 }
  826.                 else
  827.                     return false;
  828.             }
  829.             else
  830.             {
  831.                 function findRow(node) { return node.nodeType == 1 ? node : node.parentNode; }
  832.                 this.currentSearch = new TextSearch(this.panelNode, findRow);
  833.                 row = this.currentSearch.find(text);
  834.             }
  835.         }
  836.  
  837.         if (row)
  838.         {
  839.             this.document.defaultView.getSelection().selectAllChildren(row);
  840.             scrollIntoCenterView(row, this.panelNode);
  841.             return true;
  842.         }
  843.         else
  844.             return false;
  845.     }
  846. });
  847.  
  848. // ************************************************************************************************
  849.  
  850. function CSSElementPanel() {}
  851.  
  852. CSSElementPanel.prototype = extend(Firebug.CSSStyleSheetPanel.prototype,
  853. {
  854.     template: domplate(
  855.     {
  856.         cascadedTag:
  857.             DIV({},
  858.                 FOR("rule", "$rules",
  859.                     TAG("$ruleTag", {rule: "$rule"})
  860.                 ),
  861.                 FOR("section", "$inherited",
  862.                     H1({class: "cssInheritHeader groupHeader"},
  863.                         SPAN({class: "cssInheritLabel"}, "$inheritLabel"),
  864.                         TAG(FirebugReps.Element.shortTag, {object: "$section.element"})
  865.                     ),
  866.                     FOR("rule", "$section.rules",
  867.                         TAG("$ruleTag", {rule: "$rule"})
  868.                     )
  869.                 )
  870.             ),
  871.  
  872.         ruleTag:
  873.             DIV({class: "cssRule insertInto", $cssInheritedRule: "$rule.inherited",
  874.                  _repObject: "$rule.rule.style", "ruleId": "$rule.id"},
  875.                 DIV({class: "cssHead"},
  876.                     SPAN({class: "cssSelector"}, "$rule.selector"), " {",
  877.                     TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"})
  878.                 ),
  879.                 FOR("prop", "$rule.props",
  880.                     DIV({class: "cssProp editGroup", $disabledStyle: "$prop.disabled",
  881.                             $cssOverridden: "$prop.overridden"},
  882.                         SPAN({class: "cssPropName editable"}, "$prop.name"),
  883.                         SPAN({class: "cssColon"}, ":"),
  884.                         SPAN({class: "cssPropValue editable"}, "$prop.value$prop.important"),
  885.                         SPAN({class: "cssSemi"}, ";")
  886.                     )
  887.                 ),
  888.                 DIV({class: "editable insertBefore"}, "}")
  889.             ),
  890.  
  891.         computedTag:
  892.             DIV({},
  893.                 FOR("group", "$groups",
  894.                     H1({class: "cssInheritHeader groupHeader"},
  895.                         SPAN({class: "cssInheritLabel"}, "$group.title")
  896.                     ),
  897.                     TABLE({width: "100%"},
  898.                         FOR("prop", "$group.props",
  899.                             TR(
  900.                                 TD({class: "stylePropName"}, "$prop.name"),
  901.                                 TD({class: "stylePropValue"}, "$prop.value")
  902.                             )
  903.                         )
  904.                     )
  905.                 )
  906.             )
  907.     }),
  908.  
  909.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  910.  
  911.     updateCascadeView: function(element)
  912.     {
  913.         var rules = [], sections = [], usedProps = {};
  914.         this.getInheritedRules(element, sections, usedProps);
  915.         this.getElementRules(element, rules, usedProps);
  916.  
  917.         if (rules.length || sections.length)
  918.         {
  919.             var inheritLabel = $STR("InheritedFrom");
  920.             this.template.cascadedTag.replace({rules: rules, inherited: sections,
  921.                 inheritLabel: inheritLabel}, this.panelNode);
  922.         }
  923.         else
  924.             FirebugReps.Warning.tag.replace({object: "EmptyElementCSS"}, this.panelNode);
  925.     },
  926.  
  927.     updateComputedView: function(element)
  928.     {
  929.         var win = element.ownerDocument.defaultView;
  930.         var style = win.getComputedStyle(element, "");
  931.  
  932.         var groups = [];
  933.  
  934.         for (var groupName in styleGroups)
  935.         {
  936.             var title = $STR("StyleGroup-" + groupName);
  937.             var group = {title: title, props: []};
  938.             groups.push(group);
  939.  
  940.             var props = styleGroups[groupName];
  941.             for (var i = 0; i < props.length; ++i)
  942.             {
  943.                 var propName = props[i];
  944.                 var propValue = stripUnits(rgbToHex(style.getPropertyValue(propName)));
  945.                 if (propValue)
  946.                     group.props.push({name: propName, value: propValue});
  947.             }
  948.         }
  949.  
  950.         this.template.computedTag.replace({groups: groups}, this.panelNode);
  951.     },
  952.  
  953.     getStylesheetURL: function(style)
  954.     {
  955.         // if the parentStyleSheet.href is null, CSS std says its inline style
  956.         if (style && style.parentRule && style.parentRule.parentStyleSheet.href)
  957.             return style.parentRule.parentStyleSheet.href;
  958.         else
  959.             return this.selection.ownerDocument.location.href;
  960.     },
  961.  
  962.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  963.  
  964.     getInheritedRules: function(element, sections, usedProps)
  965.     {
  966.         var parent = element.parentNode;
  967.         if (parent && parent.nodeType == 1)
  968.         {
  969.             this.getInheritedRules(parent, sections, usedProps);
  970.  
  971.             var rules = [];
  972.             this.getElementRules(parent, rules, usedProps, true);
  973.  
  974.             if (rules.length)
  975.                 sections.splice(0, 0, {element: parent, rules: rules});
  976.         }
  977.     },
  978.  
  979.     getElementRules: function(element, rules, usedProps, inheritMode)
  980.     {
  981.         var inspectedRules;
  982.         try
  983.         {
  984.             inspectedRules = domUtils ? domUtils.getCSSStyleRules(element) : null;
  985.         } catch (exc) {}
  986.  
  987.         if (inspectedRules)
  988.         {
  989.             for (var i = 0; i < inspectedRules.Count(); ++i)
  990.             {
  991.                 var rule = QI(inspectedRules.GetElementAt(i), nsIDOMCSSStyleRule);
  992.  
  993.                 var href = rule.parentStyleSheet.href;  // Null means inline
  994.                 
  995.                 if (href && !Firebug.showUserAgentCSS && isSystemURL(href)) // This removes user agent rules 
  996.                     continue;
  997.                 if (!href)
  998.                     href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452
  999.  
  1000.                 var props = this.getRuleProperties(this.context, rule, inheritMode);
  1001.                 if (inheritMode && !props.length)
  1002.                     continue;
  1003.  
  1004.                 this.markOverridenProps(props, usedProps);
  1005.  
  1006.                 var line = domUtils.getRuleLine(rule);
  1007.                 var ruleId = rule.selectorText+"/"+line;
  1008.                 var sourceLink = new SourceLink(href, line, "css", rule);
  1009.                 rules.splice(0, 0, {rule: rule, id: ruleId,
  1010.                         selector: rule.selectorText, sourceLink: sourceLink,
  1011.                         props: props, inherited: inheritMode});
  1012.             }
  1013.         }
  1014.  
  1015.         this.getStyleProperties(element, rules, usedProps, inheritMode);
  1016.     },
  1017.  
  1018.     markOverridenProps: function(props, usedProps)
  1019.     {
  1020.         for (var i = 0; i < props.length; ++i)
  1021.         {
  1022.             var prop = props[i];
  1023.             if ( usedProps.hasOwnProperty(prop.name) )
  1024.             {
  1025.                 var deadProps = usedProps[prop.name];
  1026.                 for (var j = 0; j < deadProps.length; ++j)
  1027.                 {
  1028.                     var deadProp = deadProps[j];
  1029.                     if (!deadProp.disabled && deadProp.important && !prop.important)
  1030.                         prop.overridden = true;
  1031.                     else if (!prop.disabled)
  1032.                         deadProp.overridden = true;
  1033.                 }
  1034.             }
  1035.             else
  1036.                 usedProps[prop.name] = [];
  1037.  
  1038.             usedProps[prop.name].push(prop);
  1039.         }
  1040.     },
  1041.  
  1042.     getStyleProperties: function(element, rules, usedProps, inheritMode)
  1043.     {
  1044.         var props = [];
  1045.  
  1046.         var style = element.style;
  1047.         for (var i = 0; i < style.length; ++i)
  1048.         {
  1049.             var name = style.item(i);
  1050.             var value = style.getPropertyValue(name);
  1051.             var important = style.getPropertyPriority(name) == "important";
  1052.             if (value)
  1053.                 this.addProperty(name, value, important, false, inheritMode, props);
  1054.         }
  1055.  
  1056.         this.addOldProperties(this.context, getElementXPath(element), inheritMode, props);
  1057.  
  1058.         sortProperties(props);
  1059.         this.markOverridenProps(props, usedProps);
  1060.  
  1061.         if (props.length)
  1062.             rules.splice(0, 0,
  1063.                     {rule: element, id: getElementXPath(element),
  1064.                         selector: "element.style", props: props, inherited: inheritMode});
  1065.     },
  1066.  
  1067.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1068.     // extends Panel
  1069.  
  1070.     name: "css",
  1071.     parentPanel: "html",
  1072.     order: 0,
  1073.  
  1074.     show: function(state)
  1075.     {
  1076.         // Do nothing, and don't call superclass
  1077.     },
  1078.  
  1079.     supportsObject: function(object)
  1080.     {
  1081.         return object instanceof Element ? 1 : 0;
  1082.     },
  1083.  
  1084.     updateSelection: function(element)
  1085.     {
  1086.         if ( !(element instanceof Element) ) // html supports SourceLink
  1087.             return;
  1088.  
  1089.         if (sothinkInstalled)
  1090.         {
  1091.             FirebugReps.Warning.tag.replace({object: "SothinkWarning"}, this.panelNode);
  1092.             return;
  1093.         }
  1094.  
  1095.         if (!domUtils)
  1096.         {
  1097.             FirebugReps.Warning.tag.replace({object: "DOMInspectorWarning"}, this.panelNode);
  1098.             return;
  1099.         }
  1100.  
  1101.         if (!element)
  1102.             return;
  1103.  
  1104.         if (Firebug.showComputedStyle)
  1105.             this.updateComputedView(element);
  1106.         else
  1107.             this.updateCascadeView(element);
  1108.     },
  1109.  
  1110.     updateOption: function(name, value)
  1111.     {
  1112.         if (name == "showComputedStyle" || name == "showUserAgentCSS")
  1113.             this.refresh();
  1114.     },
  1115.  
  1116.     getOptionsMenuItems: function()
  1117.     {
  1118.         return [
  1119.             {label: "ShowComputedStyle", type: "checkbox", checked: Firebug.showComputedStyle,
  1120.                 command: bindFixed(Firebug.togglePref, Firebug, "showComputedStyle") },
  1121.             {label: "Show User Agent CSS", type: "checkbox", checked: Firebug.showUserAgentCSS,
  1122.                     command: bindFixed(Firebug.togglePref, Firebug, "showUserAgentCSS") }    
  1123.         ];
  1124.     }
  1125. });
  1126.  
  1127. // ************************************************************************************************
  1128. // CSSEditor
  1129.  
  1130. function CSSEditor(doc)
  1131. {
  1132.     this.initializeInline(doc);
  1133. }
  1134.  
  1135. CSSEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1136. {
  1137.     insertNewRow: function(target, insertWhere)
  1138.     {
  1139.         var emptyProp = {name: "", value: ""};
  1140.         var sibling = insertWhere == "before" ? target.previousSibling : target;
  1141.  
  1142.         return CSSPropTag.insertAfter({prop: emptyProp}, sibling);
  1143.     },
  1144.  
  1145.     saveEdit: function(target, value, previousValue)
  1146.     {
  1147.         target.innerHTML = escapeHTML(value);
  1148.  
  1149.         var row = getAncestorByClass(target, "cssProp");
  1150.         if (hasClass(row, "disabledStyle"))
  1151.             toggleClass(row, "disabledStyle");
  1152.  
  1153.         var style = Firebug.getRepObject(target);
  1154.  
  1155.         if (hasClass(target, "cssPropName"))
  1156.         {
  1157.             if (value && previousValue && previousValue != value)  // name of property has changed.
  1158.             {
  1159.                 // Get value from setProperty on previous edit OR from source of page OR undefined
  1160.                 this.previousPropertyValue = getChildByClass(row, "cssPropValue").textContent;
  1161.                 if (this.previousPropertyValue && this.previousPropertyValue != "undefined")
  1162.                 {
  1163.                     this.previousPropertyName = previousValue;
  1164.                     this.cleanUpStyle = style;
  1165.                     this.newPropertyName = value;
  1166.                 }
  1167.             }
  1168.             else if (!value) // name of the property has been deleted, so remove the property.
  1169.                 style.removeProperty(previousValue);
  1170.         }
  1171.         else if (getAncestorByClass(target, "cssPropValue"))
  1172.         {
  1173.             var propName = getChildByClass(row, "cssPropName").textContent;
  1174.             var propValue = getChildByClass(row, "cssPropValue").textContent;
  1175.  
  1176.             if (propValue)
  1177.             {
  1178.                 // XXXjoe Gecko bug workaround: Just changing priority doesn't have any effect
  1179.                 // unless we remove the property first
  1180.                 style.removeProperty(propName);
  1181.  
  1182.                 var parsedValue = parsePriority(propValue);
  1183.                 style.setProperty(propName, parsedValue.value, parsedValue.priority);
  1184.             }
  1185.             else
  1186.                 style.removeProperty(propName);
  1187.         }
  1188.  
  1189.         this.panel.markChange(this.panel.name == "stylesheet");
  1190.     },
  1191.  
  1192.     endEditing: function(currentTarget, value, cancel)
  1193.     {
  1194.         if (this.previousPropertyName)  // Might be cleaner to have different editors for names and values...
  1195.         {
  1196.             // this was a name edit. 1) new name->do nothing 2) change previous name->remove old, set new
  1197.             if (this.previousPropertyValue)
  1198.             {
  1199.                 this.cleanUpStyle.removeProperty(this.previousPropertyName);
  1200.                 var parsedValue = parsePriority(this.previousPropertyValue);
  1201.                 this.cleanUpStyle.setProperty(this.newPropertyName, parsedValue.value, parsedValue.priority);
  1202.                 delete this.previousPropertyName;
  1203.                 delete this.previousPropertyValue;
  1204.             }
  1205.         }
  1206.         // XXXjoe We need to refresh here, but can't because it interferes
  1207.         // with the tabbing.  The only reason to refresh is to update the
  1208.         // overridden flag on properties when !importants are changed, so
  1209.         // we should implement code to do this without destroying the view
  1210.  
  1211.         return true;
  1212.     },
  1213.  
  1214.     advanceToNext: function(target, charCode)
  1215.     {
  1216.         if (charCode == 58 /*":"*/ && hasClass(target, "cssPropName"))
  1217.             return true;
  1218.     },
  1219.  
  1220.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1221.  
  1222.     getAutoCompleteRange: function(value, offset)
  1223.     {
  1224.         if (hasClass(this.target, "cssPropName"))
  1225.             return {start: 0, end: value.length-1};
  1226.         else
  1227.             return parseCSSValue(value, offset);
  1228.     },
  1229.  
  1230.     getAutoCompleteList: function(preExpr, expr, postExpr)
  1231.     {
  1232.         if (hasClass(this.target, "cssPropName"))
  1233.         {
  1234.             return getCSSPropertyNames();
  1235.         }
  1236.         else
  1237.         {
  1238.             var row = getAncestorByClass(this.target, "cssProp");
  1239.             var propName = getChildByClass(row, "cssPropName").textContent;
  1240.             return getCSSKeywordsByProperty(propName);
  1241.         }
  1242.     }
  1243. });
  1244.  
  1245. // ************************************************************************************************
  1246. // StyleSheetEditor
  1247.  
  1248. function StyleSheetEditor(doc)
  1249. {
  1250.     this.box = this.tag.replace({}, doc, this);
  1251.     this.input = this.box.firstChild;
  1252. }
  1253.  
  1254. StyleSheetEditor.prototype = domplate(Firebug.BaseEditor,
  1255. {
  1256.     multiLine: true,
  1257.  
  1258.     tag: DIV(
  1259.         TEXTAREA({class: "styleSheetEditor fullPanelEditor", oninput: "$onInput"})
  1260.     ),
  1261.  
  1262.     getValue: function()
  1263.     {
  1264.         return this.input.value;
  1265.     },
  1266.  
  1267.     setValue: function(value)
  1268.     {
  1269.         return this.input.value = value;
  1270.     },
  1271.  
  1272.     show: function(target, panel, value, textSize, targetSize)
  1273.     {
  1274.         this.target = target;
  1275.         this.panel = panel;
  1276.  
  1277.         this.panel.panelNode.appendChild(this.box);
  1278.  
  1279.         this.input.value = value;
  1280.         this.input.focus();
  1281.  
  1282.         var command = this.panel.context.chrome.$("cmd_toggleCSSEditing");
  1283.         command.setAttribute("checked", true);
  1284.     },
  1285.  
  1286.     hide: function()
  1287.     {
  1288.         var chrome = this.panel.context.chrome;
  1289.         if (!chrome)
  1290.             chrome = FirebugChrome;
  1291.  
  1292.         var command = chrome.$("cmd_toggleCSSEditing");
  1293.         command.setAttribute("checked", false);
  1294.  
  1295.         if (this.box.parentNode == this.panel.panelNode)
  1296.             this.panel.panelNode.removeChild(this.box);
  1297.  
  1298.         delete this.target;
  1299.         delete this.panel;
  1300.         delete this.styleSheet;
  1301.     },
  1302.  
  1303.     saveEdit: function(target, value, previousValue)
  1304.     {
  1305.         var ownerNode = getStyleSheetOwnerNode(this.styleSheet);
  1306.  
  1307.         if (!this.styleSheet.editStyleSheet)
  1308.         {
  1309.             this.styleSheet.disabled = true;
  1310.  
  1311.             var url = CCSV("@mozilla.org/network/standard-url;1", Components.interfaces.nsIURL);
  1312.             url.spec = this.styleSheet.href;
  1313.  
  1314.             var editStyleSheet = this.editStyleSheet;
  1315.             editStyleSheet = ownerNode.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml",
  1316.                 "style");
  1317.             editStyleSheet.setAttribute("type", "text/css");
  1318.             editStyleSheet.setAttributeNS("http://www.w3.org/XML/1998/namespace", "base",
  1319.                 url.directory);
  1320.  
  1321.             // Insert the edited stylesheet directly after the old one to ensure the styles
  1322.         // cascade properly.
  1323.         ownerNode.parentNode.insertBefore(editStyleSheet, ownerNode.nextSibling);
  1324.  
  1325.             this.styleSheet.editStyleSheet = editStyleSheet;
  1326.         }
  1327.  
  1328.         this.styleSheet.editStyleSheet.innerHTML = value;
  1329.     },
  1330.  
  1331.     endEditing: function()
  1332.     {
  1333.         this.panel.refresh();
  1334.         return true;
  1335.     },
  1336.  
  1337.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1338.  
  1339.     onInput: function()
  1340.     {
  1341.         Firebug.Editor.update();
  1342.     },
  1343.  
  1344.     scrollToLine: function(line, offset)
  1345.     {
  1346.         this.startMeasuring(this.input);
  1347.         var lineHeight = this.measureText().height;
  1348.         this.stopMeasuring();
  1349.  
  1350.         this.input.scrollTop = (line * lineHeight) + offset;
  1351.     }
  1352. });
  1353.  
  1354. // ************************************************************************************************
  1355. // Local Helpers
  1356.  
  1357. function rgbToHex(value)
  1358. {
  1359.     return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, function(_, r, g, b) {
  1360.     return '#' + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase();
  1361.     });
  1362. }
  1363.  
  1364. function stripUnits(value)
  1365. {
  1366.     // remove units from '0px', '0em' etc. leave non-zero units in-tact.
  1367.     return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi, function(_, skip, remove, whitespace) {
  1368.     return skip || ('0' + whitespace);
  1369.     });
  1370. }
  1371.  
  1372. function parsePriority(value)
  1373. {
  1374.     var rePriority = /(.*?)\s*(!important)?$/;
  1375.     var m = rePriority.exec(value);
  1376.     var propValue = m ? m[1] : "";
  1377.     var priority = m && m[2] ? "important" : "";
  1378.     return {value: propValue, priority: priority};
  1379. }
  1380.  
  1381. function parseURLValue(value)
  1382. {
  1383.     var m = reURL.exec(value);
  1384.     return m ? m[1] : "";
  1385. }
  1386.  
  1387. function parseRepeatValue(value)
  1388. {
  1389.     var m = reRepeat.exec(value);
  1390.     return m ? m[0] : "";
  1391. }
  1392.  
  1393. function parseCSSValue(value, offset)
  1394. {
  1395.     var start = 0;
  1396.     var m;
  1397.     while (1)
  1398.     {
  1399.         m = reSplitCSS.exec(value);
  1400.         if (m && m.index+m[0].length < offset)
  1401.         {
  1402.             value = value.substr(m.index+m[0].length);
  1403.             start += m.index+m[0].length;
  1404.             offset -= m.index+m[0].length;
  1405.         }
  1406.         else
  1407.             break;
  1408.     }
  1409.  
  1410.     if (m)
  1411.     {
  1412.         var type;
  1413.         if (m[1])
  1414.             type = "url";
  1415.         else if (m[2] || m[3])
  1416.             type = "rgb";
  1417.         else if (m[4])
  1418.             type = "int";
  1419.  
  1420.         return {value: m[0], start: start+m.index, end: start+m.index+(m[0].length-1), type: type};
  1421.     }
  1422. }
  1423.  
  1424. function findPropByName(props, name)
  1425. {
  1426.     for (var i = 0; i < props.length; ++i)
  1427.     {
  1428.         if (props[i].name == name)
  1429.             return i;
  1430.     }
  1431. }
  1432.  
  1433. function sortProperties(props)
  1434. {
  1435.     props.sort(function(a, b)
  1436.     {
  1437.         return a.name > b.name ? 1 : -1;
  1438.     });
  1439. }
  1440.  
  1441. function getTopmostRuleLine(panelNode)
  1442. {
  1443.     for (var child = panelNode.firstChild; child; child = child.nextSibling)
  1444.     {
  1445.         if (child.offsetTop+child.offsetHeight > panelNode.scrollTop)
  1446.         {
  1447.             var rule = child.repObject ? child.repObject.parentRule : null;
  1448.             if (rule)
  1449.                 return {
  1450.                     line: domUtils.getRuleLine(rule),
  1451.                     offset: panelNode.scrollTop-child.offsetTop
  1452.                 };
  1453.         }
  1454.     }
  1455.     return 0;
  1456. }
  1457.  
  1458. function getStyleSheetCSS(sheet, context)
  1459. {
  1460.     if (sheet.ownerNode instanceof HTMLStyleElement)
  1461.         return sheet.ownerNode.innerHTML;
  1462.     else
  1463.         return context.sourceCache.load(sheet.href).join("\n");
  1464. }
  1465.  
  1466. function getStyleSheetOwnerNode(sheet) {
  1467.     for (; sheet && !sheet.ownerNode; sheet = sheet.parentStyleSheet);
  1468.  
  1469.     return sheet.ownerNode;
  1470. }
  1471.  
  1472. function scrollSelectionIntoView(panel)
  1473. {
  1474.     var selCon = getSelectionController(panel);
  1475.     selCon.scrollSelectionIntoView(
  1476.             nsISelectionController.SELECTION_NORMAL,
  1477.             nsISelectionController.SELECTION_FOCUS_REGION, true);
  1478. }
  1479.  
  1480. function getSelectionController(panel)
  1481. {
  1482.     var browser = panel.context.chrome.getPanelBrowser(panel);
  1483.     return browser.docShell.QueryInterface(nsIInterfaceRequestor)
  1484.         .getInterface(nsISelectionDisplay)
  1485.         .QueryInterface(nsISelectionController);
  1486. }
  1487.  
  1488. // ************************************************************************************************
  1489.  
  1490. Firebug.registerPanel(Firebug.CSSStyleSheetPanel);
  1491. Firebug.registerPanel(CSSElementPanel);
  1492.  
  1493. // ************************************************************************************************
  1494.  
  1495. }});
  1496.